<template>
  <a-form
    class="dy-form"
    layout="vertical"
    :hide-required-mark="isCheck"
    :class="isCheck ? 'check-detail-form' : ''"
    :scroll-to-first-error="true"
  >
    <div :class="isFlexRow ? 'md:flex md:flex-row md:flex-wrap md:-mr-11' : ''">
      <template v-for="(el, index) in formItemData" :key="el.key">
        <div
          v-if="el.subTitle"
          class="w-full mb-4.5 text-color-header font-semibold cursor-pointer"
          :class="[subTitleSize]"
          @click="toggleShow(el, index)"
        >
          {{ el.subTitle }}
          <Icon
            :type="el.show || el.show === undefined ? 'icon-caret-down' : 'icon-caret-up'"
            class="text-color-secondary"
          />
        </div>
        <a-form-item
          v-else-if="!el.hide"
          v-show="el.show || el.show === undefined"
          :class="isFlexRow ? 'md:w-1/2 md:!pr-11' : ''"
          v-bind="!disabled && validateInfos && !isCheck ? validateInfos[el.key] : null"
        >
          <template v-if="el.label || (el.checkLabel && isCheck)" #label>
            <span class="whitespace-nowrap">
              {{ isCheck ? el.checkLabel || el.label : el.label }}
            </span>
            <span v-if="curAction !== CURDAction.CHECK && getLabelDes(el)" class="text-color-secondary ml-2.5 text-xs">
              {{ getLabelDes(el) }}
            </span>
          </template>
          <div v-if="isCheck" v-html="getCheckText(el, model)"></div>
          <AInput v-else-if="el.loading" :disabled="true" :placeholder="t('common.loading')"></AInput>
          <!--3.0.0-beta.8 a-cascader 不能用component动态渲染-->
          <a-cascader
            v-else-if="el.type === FormItemComponentType.CASCADER"
            :disabled="disabled"
            v-bind="(el.props as any) || {}"
            :value="model[el.key]"
            @change="
              (e) => {
                updateModelOnChange(e, el.key);
              }
            "
          />
          <!--3.0.0-beta.8 a-tree-select用component渲染会有如下警告 warning.js:6 Warning: `children` of TreeSelect is deprecated. Please use `treeData` instead. -->
          <a-tree-select
            v-else-if="el.type === FormItemComponentType.TREESELECT"
            :disabled="disabled"
            v-bind="(el.props as any) || {}"
            :value="model[el.key]"
            @change="
              (e) => {
                updateModelOnChange(e, el.key);
              }
            "
          >
            <template #suffixIcon><Icon type="icon-xia" /></template>
          </a-tree-select>
          <component
            :is="getFormItemComponent(el)"
            v-else
            v-bind="el.props || {}"
            :ref="
              (ref) => {
                if (el.customComponent) {
                  el.customComponentRef = ref;
                }
              }
            "
            :value="el.type === FormItemComponentType.SWITCH ? undefined : model[el.key]"
            :checked="model[el.key]"
            :disabled="disabled || (el.props ? el.props.disabled : false)"
            @change="
              (e) => {
                updateModelOnChange(e, el.key);
              }
            "
            @before-upload="$emit('beforeUpload')"
            @upload="$emit('upload', $event)"
            @click="handleClick"
          >
            <template v-if="el.type === FormItemComponentType.SELECT" #suffixIcon>
              <Icon type="icon-xia" />
            </template>
            {{ el.text || "" }}
          </component>
          <component
            :is="
              typeof el.extraComponent == 'function' ? el.extraComponent({ curAction }) || emptyNode : el.extraComponent
            "
            v-if="el.extraComponent"
          />
        </a-form-item>
      </template>
    </div>
  </a-form>
</template>
<script lang="ts" setup>
import { TableColumnProps } from "ant-design-vue";
import { CURDAction, FormItemComponentType, isSelectComponent } from "/@/components/CURDDialog/models";
import type { DynamicFormExpose, FormItemData, ModelRules } from "/@/components/CURDDialog/models";
import type { TableRecord } from "/@/api/models";
import type { FormItemRule, FormRules, FormValidateHandler, SelectOptionItem } from "/@/types/antd";
import { useI18n } from "/@/locales/useI18n";
import { computed, h, onMounted, reactive, ref, shallowRef, watch } from "vue";
import EditorFormItem from "/@/components/EditorFormItem.vue";
import {
  Input as AInput,
  Radio,
  Checkbox as ACheckbox,
  DatePicker as ADatePicker,
  Select as ASelect,
  Cascader as ACascader,
  TreeSelect as ATreeSelect,
  Switch as ASwitch,
  Form,
} from "ant-design-vue";
import ImgUpload from "/@/components/common/ImgUpload.vue";
import FileUpload from "/@/components/common/FileUpload.vue";
import { getRules } from "/@/utils/verify";
import { findItemByKey } from "/@/utils/treeData";
const ARadioGroup = Radio.Group;

const props = withDefaults(
  defineProps<{
    forceFlexRow?: boolean;
    forceColumn?: boolean;
    columns?: TableColumnProps<TableRecord>[];
    model: TableRecord;
    modelFormSetting?: FormItemData[];
    modelRules?: ModelRules;
    onBeforeResetFields?: (data: FormItemData[]) => TableRecord;
    idKey?: string;
    curAction?: CURDAction;
    disabled?: boolean;
    subTitleSize?: string;
  }>(),
  {
    curAction: CURDAction.CREATE,
    subTitleSize: "text-lg",
  }
);
const emits = defineEmits(["beforeUpload", "upload", "init", "uploadSuccessFn"]);
const emptyNode = h("div", { style: "display:none" });

// const innerModel = ref<TableRecord>(props.model);
// watch(
//   innerModel,
//   (v) => {
//     emits("update:model", v);
//   },
//   {
//     deep: true,
//   }
// );
// watch(
//   () => props.model,
//   (v) => {
//     innerModel.value = v;
//   },
//   {
//     deep: true,
//   }
// );

const { t } = useI18n();
// const isCreate = computed(() => props.curAction === CURDAction.CREATE);
function getLabelDes(el: FormItemData) {
  if (el.labelDes) {
    return el.labelDes;
  }
  const isMax = el.props && el.props.max && el.props.max > 1;
  if (el.type === FormItemComponentType.IMGUPLOAD) {
    return t("components.uploadImg.uploadTips") + (isMax ? "，按住ctrl可多选" : "");
  } else if (el.type === FormItemComponentType.FILEUPLOAD) {
    return el.labelDes + (isMax ? "，按住ctrl可多选" : "");
  }
  return "";
}
const isCheck = computed(() => props.curAction === CURDAction.CHECK);
const isFlexRow = computed(() => {
  return (
    ((formItemData.value.length > 4 && props.curAction === CURDAction.CREATE) || props.forceFlexRow) &&
    !props.forceColumn
  );
});

function toggleShow(el: FormItemData, index: number) {
  // if (el.show === undefined) {
  //   el.show = true;
  // }
  // el.show = !el.show;
  // for (let i = index + 1; i < formItemData.value.length; i++) {
  //   const item = formItemData.value[i];
  //   if (item.subTitle) {
  //     break;
  //   }
  //   item.show = el.show;
  // }
}

function updateModelOnChange(e, key) {
  let value;
  if (e && e.target) {
    value = e.target.value;
    if (value === undefined) {
      value = e.target.checked;
    }
  } else {
    value = e;
  }
  Object.assign(props.model, {
    [key]: value,
  });
}

/**
 * init form
 */
const formItemData = ref<FormItemData[]>([]);
const validate = shallowRef<FormValidateHandler>();
const validateInfos = ref<Record<string, any>>({});
const resetFields = ref<(newValues?: any) => void>();
let formRules = reactive<FormRules>({});

function initForm() {
  // console.log("==========>dynamicForm init");
  let data: FormItemData[] = [];
  for (let key in props.model) {
    if (key === props.idKey) continue;
    const item: FormItemData = {
      label: "",
      key,
    };
    if (props.columns) {
      for (let i = 0; i < props.columns.length; i++) {
        if (props.columns[i].dataIndex === key) {
          item.label = props.columns[i].title as string;
          break;
        }
      }
    }
    data.push(item);
  }
  if (props.modelFormSetting) {
    const modelFormSetting = [...props.modelFormSetting];
    for (let j = 0; j < modelFormSetting.length; j++) {
      const item = modelFormSetting[j];
      if (item.subTitle) continue;
      if (!item.props) {
        item.props = {};
      }
      if (item.type === FormItemComponentType.DATEPICKER || item.type === FormItemComponentType.RANGEPICKER) {
        if (item.props.valueFormat === undefined) {
          item.props.valueFormat = t("common.dateTimeFormatStr");
        }
      }
      if (!item.props.placeholder) {
        if (
          item.type === FormItemComponentType.DATEPICKER ||
          item.type === FormItemComponentType.SELECT ||
          item.type === FormItemComponentType.TREESELECT
        ) {
          item.props.placeholder = "请选择" + (item.props.mode === "multiple" ? "(可多选)" : "");
        } else if (
          !item.type ||
          item.type === FormItemComponentType.TEXT ||
          item.type === FormItemComponentType.TEXTAREA
        ) {
          item.props.placeholder = "请输入";
        }
      }
      let isExist = false;
      for (let i = 0; i < data.length; i++) {
        if (item.key === data[i].key) {
          isExist = true;
          Object.assign(data[i], item);
          break;
        }
      }
      if (!isExist) {
        data.push(item);
      }
    }
    //处理subTitle
    modelFormSetting.forEach((el) => {
      if (el.key === "_subTitle" && el.subTitle && el.beforeKey) {
        const index = data.findIndex((item) => item.key === el.beforeKey);
        index !== -1 && data.splice(index, 0, el);
      }
    });
  }
  formItemData.value = data;
  //表单校验规则
  const rules: FormRules = {};
  props.modelRules?.forEach((item) => {
    if (typeof item === "string") {
      const formItem = formItemData.value.find((el) => el.key === item);
      if (formItem) {
        rules[item] = getRules({
          required: true,
          pattern: formItem.pattern,
          messageName: isSelectComponent(formItem.type as FormItemComponentType) ? "" : formItem.label,
          selectMessageName: formItem.label,
          min: formItem.min,
          max: formItem.max,
          requiredMes: formItem.requiredMes,
        });
      } else {
        rules[item] = [
          {
            required: true,
            message: t("common.required"),
          },
        ];
      }
    } else if (typeof item === "object") {
      let key: string = "";
      for (let i in item) {
        key = i;
        break;
      }
      if (key) {
        let target = item[key];
        const formItem = formItemData.value.find((el) => el.key === key);
        if (formItem) {
          target = getRules({
            required: formItem.required,
            pattern: formItem.pattern,
            messageName: isSelectComponent(formItem.type as FormItemComponentType) ? "" : formItem.label,
            selectMessageName: formItem.label,
            min: formItem.min,
            max: formItem.max,
            requiredMes: formItem.requiredMes,
          }).concat(target);
          item[key] = target;
        }
        Object.assign(rules, item);
      }
    }
  });
  formItemData.value.forEach((item) => {
    if ((item.pattern || item.min || item.max || item.required) && !rules[item.key]) {
      rules[item.key] = getRules({
        pattern: item.pattern,
        messageName: isSelectComponent(item.type as FormItemComponentType) ? "" : item.label,
        selectMessageName: item.label,
        min: item.min,
        max: item.max,
        required: item.required,
        requiredMes: item.requiredMes,
      });
    }
  });
  formRules = reactive(rules);
  const result = Form.useForm(props.model, formRules);
  validate.value = result.validate;
  validateInfos.value = result.validateInfos;
  resetFields.value = result.resetFields;
}
initForm();
watch([() => props.model, () => props.modelFormSetting, () => props.modelRules], () => {
  initForm();
});

function getFormItemComponent(el: FormItemData) {
  if (el.customComponent) {
    return el.customComponent;
  }
  if (!el.type || el.type === FormItemComponentType.TEXT) {
    return AInput;
  }
  if (el.type === FormItemComponentType.TEXTAREA) {
    return AInput.TextArea;
  }
  if (el.type === FormItemComponentType.SELECT && el.props && el.props.options) {
    return el.props.options.length <= 3 && el.props.mode !== "multiple" ? ARadioGroup : ASelect;
  }
  if (el.type === FormItemComponentType.RADIO && el.props && el.props.options) {
    return ARadioGroup;
  }
  if (el.type === FormItemComponentType.DATEPICKER) {
    return ADatePicker;
  }
  if (el.type === FormItemComponentType.CHECKBOX) {
    return ACheckbox;
  }
  if (el.type === FormItemComponentType.CASCADER) {
    return ACascader;
  }
  if (el.type === FormItemComponentType.RANGEPICKER) {
    return ADatePicker.RangePicker;
  }
  if (el.type === FormItemComponentType.IMGUPLOAD) {
    return ImgUpload;
  }
  if (el.type === FormItemComponentType.TREESELECT) {
    return ATreeSelect;
  }
  if (el.type === FormItemComponentType.PASSWORD) {
    return AInput.Password;
  }
  if (el.type === FormItemComponentType.SWITCH) {
    return ASwitch;
  }
  if (el.type === FormItemComponentType.EDITOR) {
    return EditorFormItem;
  }
  if (el.type === FormItemComponentType.FILEUPLOAD) {
    return FileUpload;
  }
  return AInput;
}
//初始化需要异步加载数据的组件，比如Select
async function initFormItemComponentData() {
  const tasks: Promise<boolean>[] = [];
  for (let i = 0; i < formItemData.value.length; i++) {
    const el = formItemData.value[i];
    if (el.props && el.getData) {
      const p = new Promise<boolean>((resolve) => {
        if (!el.getData) {
          resolve(true);
          return;
        }
        el.loading = true;
        el.getData({ action: props.curAction, item: el })
          .then((data) => {
            if (data && el.props) {
              if (el.type === FormItemComponentType.TREESELECT) {
                el.props.treeData = data;
              } else if (el.customComponent) {
                el.props.data = data;
              } else {
                el.props.options = data;
              }
            }
            resolve(true);
          })
          .finally(() => {
            el.loading = false;
          });
      });
      tasks.push(p);
    }
  }
  return Promise.all(tasks);
}
function getCheckText(el: FormItemData, model: TableRecord) {
  //el.text || model[el.key]
  let val: string | number = "";
  if (el.getText) {
    val = el.getText(model[el.key], model);
  } else {
    val = el.text || model[el.key];
  }
  if (val === null || val === undefined || val === "") {
    return "--";
  }
  return val;
}
const requiredFields: { [key: string]: number } = {};
onMounted(() => {
  emits("init");
});
function handleClick(e) {
  const inputEl = e.target as HTMLInputElement;
  if (inputEl.classList.contains("ant-input")) {
    inputEl.select();
  }
}
const dyExpose: DynamicFormExpose = {
  getOptionItemByValue(forItemKey, optionValue) {
    const item = formItemData.value.find((el) => el.key === forItemKey);
    if (item && item.props) {
      const options = item.props.options as SelectOptionItem[];
      if (options && options.length > 0) {
        return options.find((el) => typeof el === "object" && el.value === optionValue);
      }
    }
  },
  reloadFormItemComponentData(formItemKey, params) {
    const item = formItemData.value.find((el) => el.key === formItemKey);
    if (item && item.getData) {
      item.loading = true;
      item
        .getData({ action: props.curAction, item, params })
        .then((data) => {
          if (data && item.props) {
            if (item.type === FormItemComponentType.TREESELECT) {
              item.props.treeData = data;
            } else if (item.customComponent) {
              item.props.data = data;
            } else {
              item.props.options = data;
            }
            //处理查看模式下的文字
            if (item.textKey && props.model[formItemKey] !== undefined) {
              const info = findItemByKey(data, props.model[formItemKey], formItemKey);
              if (info) {
                item.text = info.item[item.textKey];
              } else {
                item.text = "--";
              }
            }
          }
        })
        .finally(() => {
          item.loading = false;
        });
    }
  },
  validate(names?, option?) {
    if (validate.value) {
      return validate.value(names, option);
    }
    return Promise.resolve();
  },
  async validateComponent() {
    for (let i = 0; i < formItemData.value.length; i++) {
      const el = formItemData.value[i];
      if (el.customComponentRef && el.customComponentRef.validate) {
        try {
          await el.customComponentRef.validate();
        } catch (err) {
          return false;
        }
      }
    }
    return true;
  },
  resetFields() {
    if (resetFields.value) {
      let newValues: TableRecord | undefined;
      if (props.onBeforeResetFields) {
        newValues = props.onBeforeResetFields(formItemData.value);
      }
      resetFields.value(newValues);
    }
    for (let i = 0; i < formItemData.value.length; i++) {
      const el = formItemData.value[i];
      if (el.customComponentRef && el.customComponentRef.clear) {
        el.customComponentRef.clear();
      }
    }
  },
  getFormItemData() {
    return formItemData.value;
  },
  setFieldRquired(key: string, required = false) {
    const rules = formRules[key];
    if (rules) {
      rules.forEach((rule) => {
        rule.required = required;
      });
    }
    const validateInfo = validateInfos[key];
    if (validateInfo) {
      validateInfo.required = required;
    }
  },
  initFormItemComponentData,
  //隐藏或显示字段
  toggleField(key, hide) {
    const item = formItemData.value.find((el) => el.key === key);
    if (!item) {
      return;
    }
    if (hide) {
      //隐藏要去掉必填规则
      const validateInfo = validateInfos[key];
      if (validateInfo && validateInfo.required) {
        requiredFields[key] = 1;
      }
      dyExpose.setFieldRquired(key, false);
    } else {
      if (requiredFields[key]) {
        //还原必填规则
        dyExpose.setFieldRquired(key, true);
        delete requiredFields[key];
      }
    }
    item.hide = hide;
  },
};
defineExpose(dyExpose);
</script>
<style lang="less">
.dy-form {
  .ant-form-item-explain {
    //触发多个错误的时候 只显示第一个 .ant-form-item-explain-error
    height: 24px;
    overflow: hidden;
  }
  .ant-checkbox-group {
    .ant-checkbox-wrapper {
      margin-left: 0;
    }
  }
  .ant-form-item-label > label {
    display: inline-flex;
    flex-direction: row;
    align-items: center;
  }
}
</style>
